// This is unpublished source code. Michah Lerner 2006

package trieMatch.keywordMatcher;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import trieMatch.Interfaces.MapFactory;

/**
 * Construct lexical trie from content from sourceFile.<br>
 * 
 * <b>NOTE:</b>this code configures and specifies match criterion through <code>enum <b>methods</b></code>. New
 * methods can be defined as additional constants for <code>enum <b>methods</b></code>. This class does not
 * currently use Guice for annotation-based configuration. <br>
 * <br>
 * 
 * The <code>enum MatchType</code> methods override <code>abstract MapFactory getMapFactory</code> and
 * <code>TrieMatcher select</code> for each type of match. The <b>enum</b> constants denote standard and Inorder
 * constraints on sets and sequences.<br>
 * 
 * @author Michah.Lerner
 * 
 */
public class KeyMatch {
	public static Map<String, String> symbolToName = new HashMap<String, String>();
	public static Integer nItems = null;
	String sourceFilename = null;

	public Tiers tiers;
	final public Sequencer elementIndexer;

	static Long freememory;

	/**
	 * Allocate and read. Makes a new keymatcher, and initializes it with the contents of the given file.
	 * 
	 * @param filename
	 *            a file containing keymatch terms [keys,type,name,description]
	 * @throws IOException
	 */
	public KeyMatch(final String filename) throws IOException {
		elementIndexer = new Sequencer();
		tiers = new Tiers();
		readKeywordMatches(filename);
	}

	/**
	 * Allocates a keymatch with a full copy of the tier structure. Suitable only for shipping the structure to another
	 * CPU in the non-shared memory environment. Do not use this function in the shared memory environment.
	 * 
	 * @param km
	 */
	public KeyMatch(final KeyMatch km) {
		elementIndexer = new Sequencer();
		tiers = new Tiers(km.tiers);
	}

	public enum MatchType {
		/** Accepts when the template matches a subset of the input text */
		KEYWORDMATCH,
		/** Accepts when the template matches an ordered subset of the input text (first-to-last, skipped words OK) */
		INORDERMATCH,
		/** Accepts when template matches a sequence of input text (can skip at start and end, not between words) */
		PHRASEMATCH,
		/** Accept only when the template matches all words of the input, in the same order of occurrence */
		EXACTMATCH;
	}

	/**
	 * Allow preprocessing of input text according to the code given. For testing.
	 * 
	 * @author Michah.Lerner
	 * 
	 */
	public enum EvalType {
		eNONE,
		/** input */
		eINP,
		/** combinations */
		eCOM,
		/** permutations */
		ePCOM
	}

	public enum MatcherActionDefinition {
		/** Search for the longest match, ignoring the accumulation multiple matches to the same entity */
		longest {
			@Override
			public MapFactory getMapFactory() {
				return new SortedtrieMatchLONGEST().mapFactory();
			}

			@Override
			public TrieMatcher select(final Tiers tiers, final Sequencer sequencer) {
				return new SortedtrieMatchLONGEST(tiers, sequencer);
			}

			@Override
			public TrieMatcher select(final Tiers tiers) {
				return new SortedtrieMatchLONGEST(tiers);
			}
		},
		/** Acquire all matches, accumulating the partial scores of multiple matches to the same entity */
		all {
			@Override
			public MapFactory getMapFactory() {
				return new TrieMatcher().mapFactory();
			}

			@Override
			public TrieMatcher select(final Tiers tiers, final Sequencer sequencer) {
				return new TrieMatcher(tiers, sequencer);
			}

			@Override
			public TrieMatcher select(final Tiers tiers) {
				return new TrieMatcher(tiers);
			}
		},
		/** Return just the first result(s) of the <code>all</code> form of matcher */
		top {
			@Override
			public MapFactory getMapFactory() {
				return new SortedtrieMatchTOP().mapFactory();
			}

			@Override
			public TrieMatcher select(final Tiers tiers, final Sequencer sequencer) {
				return new SortedtrieMatchTOP(tiers, sequencer);
			}

			@Override
			public TrieMatcher select(final Tiers tiers) {
				return new SortedtrieMatchTOP(tiers);
			}
		},
		exp {
			@Override
			public MapFactory getMapFactory() {
				return new SortedtrieMatchEXP().mapFactory();
			}

			@Override
			public TrieMatcher select(final Tiers tiers, final Sequencer sequencer) {
				return new SortedtrieMatchEXP(tiers, sequencer);
			}

			@Override
			public TrieMatcher select(final Tiers tiers) {
				return new SortedtrieMatchEXP(tiers);
			}
		};
		public abstract MapFactory getMapFactory();

		public abstract TrieMatcher select(Tiers tiers, Sequencer sequencer);

		public abstract TrieMatcher select(Tiers tiers);
	}

	public String getDescription(final String name) {
		return symbolToName.get(name);
	}

	public static void doGC() {
		Runtime.getRuntime().gc();
		if (freememory == null) {
			freememory = Runtime.getRuntime().freeMemory();
		} else {
			System.out.println("Consumed " + (freememory - Runtime.getRuntime().freeMemory()));
		}
	}

	void readKeywordMatches(final String filename) throws IOException {
		boolean outOfMemory = false;
		BufferedReader input;
		int lineNo = 0;

		try {
			try {
				input = openInput(filename);
				String line;
				KMDefinition kmDef;
				while (input.ready()) {
					line = input.readLine();
					lineNo++;
					if (line == null || line.length() == 0) {
						continue;
					}
					kmDef = new KMDefinition(elementIndexer, line);
					tiers.add(kmDef);
				}
				input.close();
				// tiers.printTiers(System.out);
			} catch (final Exception e) {
				try {
					System.out.println("error reading line " + lineNo + " " + (new File(filename)).getCanonicalPath());
					System.out.println(e.getMessage());
				} catch (final IOException e1) {
					e1.printStackTrace();
					throw new IOException(filename);
				}
			}
			nItems = lineNo;
			sourceFilename = filename;
		} catch (final OutOfMemoryError e) {
			outOfMemory = true;
		} finally {
			if (outOfMemory) {
				System.out.println("error after " + lineNo + " input lines.");
			}
		}
	}

	static boolean stdFilename(final String filename) {
		return filename == null || filename.length() == 0 || filename.equals("-");
	}

	public static BufferedReader openInput(final String filename) throws IOException {
		return new BufferedReader((stdFilename(filename)
				? (new InputStreamReader(System.in))
				: (new FileReader(filename))));
	}

}
